!pr0
!lm12
!rm75
Displaying Character Generator EPROMs......Bob Sander-Cederlof

We make our own Character Generator EPROMs for Revision 7 or later Apple II Plusses.  I use the Mountain Hardware EPROM Burner to burn the data into 2716 EPROMs.  We have several different character sets, and it can be a lot of trouble to check the results.

After designing a character set, and formatting all the bits into the 2048 bytes of EPROM space, and burning it in, we still have to take an Apple apart and plug the chip in to see if all the characters look right.

I decided to write a program which would map the EPROM data onto the hi-res screen, allowing me to test without wasting time burning/erasing EPROMs and dismantling/re-assembling my Apple.

Even if you don't have the same requirements, you can learn a lot about indexing techniques and address shuffling from studying the following program.

Starting at the top....  I set up three page-zero variables in lines 1040-1060.  The S-C Macro Assembler is a great environment for making short programs like this one, because I can cycle through edit-assemble-test until it works just right without ever leaving the assembler.  S-C Macro allows me to use zero-page locations $00-$1F without fear of inteference ($00-$1E in the Apple //e).

Lines 1080 and 1090 define two buffers where I BLOAD two different EPROM images.  I put one at $6800-6FFF, the other at $7000-77FF.  There is room on the screen to display one character set in a 16x16 matrix on the left side, and the other on the right side.

For grins, I decided to use the subroutine in Applesoft ROM at $F3E2 to turn on hi-res mode.  This is the code executed for the HGR statement, so I called it AS.HGR at line 1110.  HGR sets all the soft-switches to hi-res page 1, and clears the screen.

Lines 1160-1180 call the HGR subroutine.  Since I was using S-C Macro in the RAM card, and since the Applesoft ROMs are not switched on when a program is executing in the RAM card, I had a problem.  The first time I tried to run DISPLAY, I left out lines 1160 and 1180.  The result was a total disaster.  Line 1170 did a JSR $F3E2 into the RAM card!  I had to RESET and reboot the computer to get control again.  Look out for these kinds of problems whenever you are trying to use code in both places at once.

Lines 1190-1280 set up the starting addresses to display the first character set on the left half of the screen.  Lines 1290-1380 do the same job to show the second set on the right half-screen.
!np
The top line of hi-res page 1 starts at $2000, and goes to $2027.  The middle of the line starts at $2014.  The starting addresses of subsequent lines can be computed from these two base addresses, although it is a little tricky.  More on this later.

The hi-res screen shows the least significant seven bits from each byte.  There are forty bytes in each line, making a total of 280 dots across.  The dots in each byte are in reverse order:  the least significant bit is the leftmost dot.  On the other hand, the EPROM image is in normal order.  The subroutine DISPLAY.ONE.SET takes care of all the addressing, and REVERSE.BITS handles the reversals.

Lines 1400-1410 pause until I hit any key on the keyboard.  During this pause I can examine the screen as long as I wish.  When I type any key, the keyboard strobe will be set and $C000 will go negative.  Line 1420 will then clear the keyboard strobe, and the RTS at line 1430 returns to the S-C Macro Assembler.

This brings us to a closer examination of the subroutine to actually display a character set, in lines 1440-1770.  We will be displaying 16 rows of characters, with 16 characters in each row.  It is therefore natural to simplify the problem by writing another subroutine to display one row of characters, and call it sixteen times.

Lines 1480 and 1490 start a loop much like Applesoft's FOR I = 1 TO 16...except in assembly language it is easier to go from 16 to 1.  The equivalent to NEXT I is at lines 1750 and 1760, where CNT16 is decremented.  In between we have the body of the loop.

Line 1500 calls DISPLAY.ONE.ROW, a subroutine that only gets called from this one line.  I made it into a separate subroutine so I could put off writing it until later, and concentrate on one loop at a time.  DISPLAY.ONE.ROW expects the addresses at SCREEN.ADR and EPROM.ADR to be already set up for the first byte to be displayed in the current row.  After it returns, those addresses will have been modified.

Lines 1510-1580 add 15*8, or 120, to the address in EPROM.ADR.  DISPLAY.ONE.ROW already added 8, so the total augment is 128.  This moves us up to the beginning of the next set of sixteen characters.

Lines 1590-1740 assume that DISPLAY.ONE.ROW already added $2000 to the address in SCREEN.ADR, and subtracts that value back out.  At the same time, we add back in $80, to move to the next group of eight screen lines for the next row of characters.  This is sufficient for the first eight rows of characters, but in moving to the ninth row there is a discontinuity which requires adding $28 and subtracting $400 to get the right address.  The fact that the ninth row has arrived is apparent by the fact that the high byte of the address goes above $23 (lines 1670 and 1680).

Here is a table of the starting addresses for each of the 24 character rows (we only use the first 16):

!lm+5
Row  Address     Row  Address     Row  Address
 1    $2000       9    $2028      17    $2050
 2    $2080      10    $20A8      18    $20D0
 3    $2100      11    $2128      19    $2150
 4    $2180      12    $21A8      20    $21D0
 5    $2200      13    $2228      21    $2250
 6    $2280      14    $22A8      22    $22D0
 7    $2300      15    $2328      23    $2350
 8    $2380      16    $23A8      24    $23D0
!lm-5

The starting addresses for the right half-screen can be obtained by just adding $14 to all of the above addresses.  What we do is START at $2014, and all the rest are computed automatically.

Now we can talk about what goes on inside one row of characters.  Lines 1810-2000 do the job of moving bytes from the EPROM image to the eight screen lines which form the row of characters.  Lines 1820-1830 start a loop to count out eight repetitions, and lines 1980-1990 perform the NEXT on this loop.

On each pass through the loop the subroutine GET.PUT is called sixteen times to move a byte for each character to the screen image.  GET.PUT is another subroutine only called from one place, but made into a subroutine for ease of understanding.  The inner loop of 0 through 15 is controlled by the X-register.  Line 1850 sets X=0, and lines 1910-1930 increment, test, and branch ("NEXT X" sequence).  The X-register also indexes the STA instruction inside GET.PUT, so that the screen byte for each character is stored into the right place on the screen line.  The Y-register is used as an index into the EPROM data by GET.PUT, and parallels the X-register but with an increment of 8 rather than 1.  Lines 1870-1900 bump the Y-register by 8 each time through the inner loop.

GET.PUT (lines 2230-2340) does the very simple job of moving one byte from one place in memory to another.  Or is it so simple....  Notice that the addresses inside the LDA and STA instructions are filled in when the program runs.  This is called self-modifying code, and I normally avoid such code at all costs.  It can lead to all sorts of devastating things.  Nevertheless, there are exceptions to most rules, and a time for nearly everything.  This is one of those, I think.  Isolating the offensive code into its own little subroutine appeases my conscience somewhat.

In between LDA and STA I call REVERSE.BITS, yet another simple subroutine which could be written in-line.  I prefer making it separate for nicer modularity.  The comments show what is going on, bit-by-bit.  If you were working from character generator data written for the DOS TOOL KIT or HIGHER TEXT, the bits would already be in the right order.  It is just because I am using data for the character generator EPROM that we need to reverse the bits.

Here is a printout done with my NEC PC-8023 and a Grappler+ interface card.  The two character sets shown are the ones we sell.  The one on the left uses regular lower case characters, with descenders.  All the lower case characters are raised up one screen line to leave room for the descenders.  The set on the right uses small caps for the lower case, and is the one we use in all the Apples here.  The first four rows are the characters used in INVERSE mode, and the next four rows are for FLASH mode.  (Doesn't flash too well on paper!)







<character sets and program follow>
